/* -------------------------------------------------------------------------
 *
 * win32_sema.c
 *    Microsoft Windows Win32 Semaphores Emulation
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *    src/common/backend/port/win32_sema.c
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"
#include "knl/knl_variable.h"

#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/lock/pg_sema.h"

static HANDLE* mySemSet; /* IDs of sema sets acquired so far */
static int numSems;      /* number of sema sets acquired so far */
static int maxSems;      /* allocated size of mySemaSet array */

static void ReleaseSemaphores(int code, Datum arg);

/*
 * PGReserveSemaphores --- initialize semaphore support
 *
 * In the Win32 implementation, we acquire semaphores on-demand; the
 * maxSemas parameter is just used to size the array that keeps track of
 * acquired semas for subsequent releasing.  We use anonymous semaphores
 * so the semaphores are automatically freed when the last referencing
 * process exits.
 */
void PGReserveSemaphores(int maxSemas, int port)
{
    mySemSet = (HANDLE*)malloc(maxSemas * sizeof(HANDLE));
    if (mySemSet == NULL)
        ereport(PANIC, (errmsg("out of memory")));
    numSems = 0;
    maxSems = maxSemas;

    on_shmem_exit(ReleaseSemaphores, 0);
}

/*
 * Release semaphores at shutdown or shmem reinitialization
 *
 * (called as an on_shmem_exit callback, hence funny argument list)
 */
static void ReleaseSemaphores(int code, Datum arg)
{
    int i;

    for (i = 0; i < numSems; i++)
        CloseHandle(mySemSet[i]);
    free(mySemSet);
    mySemSet = NULL;
}

/*
 * PGSemaphoreCreate
 *
 * Initialize a PGSemaphore structure to represent a sema with count 1
 */
void PGSemaphoreCreate(PGSemaphore sema)
{
    HANDLE cur_handle = NULL;
    SECURITY_ATTRIBUTES sec_attrs;

    /* Can't do this in a backend, because static state is postmaster's */
    Assert(!IsUnderPostmaster);

    if (numSems >= maxSems)
        ereport(PANIC, (errmsg("too many semaphores created")));

    ZeroMemory(&sec_attrs, sizeof(sec_attrs));
    sec_attrs.nLength = sizeof(sec_attrs);
    sec_attrs.lpSecurityDescriptor = NULL;
    sec_attrs.bInheritHandle = TRUE;

    /* We don't need a named semaphore */
    cur_handle = CreateSemaphore(&sec_attrs, 1, 32767, NULL);
    if (cur_handle) {
        /* Successfully done */
        *sema = cur_handle;
        mySemSet[numSems++] = cur_handle;
    } else
        ereport(PANIC, (errmsg("could not create semaphore: error code %d", (int)GetLastError())));
}

/*
 * PGSemaphoreReset
 *
 * Reset a previously-initialized PGSemaphore to have count 0
 */
void PGSemaphoreReset(PGSemaphore sema)
{
    /*
     * There's no direct API for this in Win32, so we have to ratchet the
     * semaphore down to 0 with repeated trylock's.
     */
    while (PGSemaphoreTryLock(sema))
        ;
}

/*
 * PGSemaphoreLock
 *
 * Lock a semaphore (decrement count), blocking if count would be < 0.
 * Serve the interrupt if interruptOK is true.
 */
void PGSemaphoreLock(PGSemaphore sema, bool interruptOK)
{
    DWORD ret;
    HANDLE wh[2];

    /*
     * Note: pgwin32_signal_event should be first to ensure that it will be
     * reported when multiple events are set.  We want to guarantee that
     * pending signals are serviced.
     */
    wh[0] = pgwin32_signal_event;
    wh[1] = *sema;

    /*
     * As in other implementations of PGSemaphoreLock, we need to check for
     * cancel/die interrupts each time through the loop.  But here, there is
     * no hidden magic about whether the syscall will internally service a
     * signal --- we do that ourselves.
     */
    do {
        t_thrd.int_cxt.ImmediateInterruptOK = interruptOK;
        CHECK_FOR_INTERRUPTS();

        ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
        if (ret == WAIT_OBJECT_0) {
            /* Signal event is set - we have a signal to deliver */
            pgwin32_dispatch_queued_signals();
            errno = EINTR;
        } else if (ret == WAIT_OBJECT_0 + 1) {
            /* We got it! */
            errno = 0;
        } else
            /* Otherwise we are in trouble */
            errno = EIDRM;

        t_thrd.int_cxt.ImmediateInterruptOK = false;
    } while (errno == EINTR);

    if (errno != 0)
        ereport(FATAL, (errmsg("could not lock semaphore: error code %d", (int)GetLastError())));
}

/*
 * PGSemaphoreUnlock
 *
 * Unlock a semaphore (increment count)
 */
void PGSemaphoreUnlock(PGSemaphore sema)
{
    if (!ReleaseSemaphore(*sema, 1, NULL))
        ereport(FATAL, (errmsg("could not unlock semaphore: error code %d", (int)GetLastError())));
}

/*
 * PGSemaphoreTryLock
 *
 * Lock a semaphore only if able to do so without blocking
 */
bool PGSemaphoreTryLock(PGSemaphore sema)
{
    DWORD ret;

    ret = WaitForSingleObject(*sema, 0);
    if (ret == WAIT_OBJECT_0) {
        /* We got it! */
        return true;
    } else if (ret == WAIT_TIMEOUT) {
        /* Can't get it */
        errno = EAGAIN;
        return false;
    }

    /* Otherwise we are in trouble */
    ereport(FATAL, (errmsg("could not try-lock semaphore: error code %d", (int)GetLastError())));

    /* keep compiler quiet */
    return false;
}
